package examples

import java.io.BufferedReader
import scalaz.effects._
import java.io.File
import java.io.FileReader
import java.io.Reader

object IterateeExample {
  import scalaz._
  import Scalaz._
  import IterV._

  def main(args: Array[String]) = {
    run1
    run2
  }

  def enumerate[E,A]: (List[E], IterV[E,A]) => IterV[E,A] = {
    case (Nil, i) => i
    case (_, i@Done(_, _)) => i
    case (x :: xs, Cont(k)) => enumerate(xs, k(El(x)))
  }

  def drop1Keep1[E]: IterV[E, Option[E]] = for {
    _ <- drop1
    x <- head
  } yield x

  def swapHeads[E]: IterV[E, (Option[E], Option[E])] = for {
    x <- head
    y <- head
  } yield (x, y)

  /** An iteratee that skips the first n elements of the input **/
  def drop1[E] : IterV[E, Unit] = {
    def step(s: Input[E]): IterV[E, Unit] =
      s(el = _ => Done((), IterV.Empty[E]),
        empty = Cont(step),
        eof = Done((), EOF[E]))
    Cont(step)
  }
  
  def empty[E]: IterV[E, Unit] = Done((), IterV.Empty[E])
  
  def run1: Unit = {
    println(enumerate(List.range(1,15), drop1Keep1[Int]).run)
    println(enumerate(List.range(1,15), head[Int]).run)
    println(enumerate(List.range(1,15), drop1Keep1[Int]).run)
    println(enumerate(List.range(1,15), swapHeads[Int]).run)
  }

  implicit val StreamEnumerator = new Enumerator[Stream] {
    def apply[E, A](e: Stream[E], i: IterV[E, A]): IterV[E, A] = e match {
      case Stream() => i
      case x #:: xs => i.fold(done = (_, _) => i, cont = k => apply(xs, k(El(x))))
    }
  }

  val list = collect[Int, List]
  val reverse = reversed[Int, List](ListReducer)
  val repeatHead = repeat[Int, Option[Int], List](head)

  def enumReader[A](r: BufferedReader, it: IterV[String, A]): IO[IterV[String, A]] = {
    def loop: IterV[String, A] => IO[IterV[String, A]] = {
      case i@Done(_, _) => i.pure[IO]
      case i@Cont(k) => for {
        s <- io { r.readLine }
        a <- if (s == null) i.pure[IO] else loop(k(El(s)))
      } yield a
    }
    loop(it)
  }

  def bufferFile(f: File) = io {
    new BufferedReader(new FileReader(f))
  }
   
  def closeReader(r: Reader) = io {
    r.close
  }
   
  def bracket[A,B,C](init: IO[A], fin: A => IO[B], body: A => IO[C]): IO[C] =
  for { a <- init
        c <- body(a)
        _ <- fin(a) }
    yield c
   
  def enumFile[A](f: File, i: IterV[String, A]): IO[IterV[String, A]] =
    bracket(bufferFile(f),
            closeReader(_:BufferedReader),
            enumReader(_:BufferedReader, i))
  
  def run2 {
    head(Stream(1, 2, 3)).run assert_=== Some(1)
    length(Stream(10, 20, 30)).run assert_=== 3
    peek(Stream(1, 2, 3)).run assert_=== Some(1)
    head(Stream[Int]()).run assert_=== none[Int]
    list(Stream(1, 2, 3)).run assert_=== List(1, 2, 3)
    repeatHead(Stream(1, 2, 3)).run assert_=== List(Some(1), Some(2), Some(3))
    reverse(Stream(1, 2, 3)).run assert_=== List(3, 2, 1)

    // As a monad
    val m1 = head[Int] >>= ((b:Option[Int]) => head[Int] map (b2 => (b <|*|> b2)))
    m1(Stream(1,2,3)).run assert_=== Some(1 -> 2)
    
    val res = enumFile(new File("src/examples/IterateeExample.scala"), head) map (_.run)
    val result = res.unsafePerformIO
    println(result)
  }
}
